/*
	Copyright (c) 2011 Andrey O. Zbitnev (azbitnev@gmail.com)
	Licensed under the MIT License (LICENSE.txt).

	Project home: http://js-cron.googlecode.com/

	Version: 1.2.0

	$Id: js-cron.js 6 2014-09-17 12:48:10Z azbitnev $
*/

var CRONTAB_FILE = 'C:/main/opt/js-cron/js-cron.tab'; // path to crontab
var LOG_FILE = 'C:/main/opt/js-cron/js-cron.log'; // path to log file
var LOG_LEVEL = 3; // 4 - DEBUG, 3 - INFO, 2 - WARNING, 1 - ERROR, 0 - none

(function () {

	// cron variables

	var tasks = []
	var reboot = true

	// init ActiveX objects

	var sh = new ActiveXObject('WScript.Shell')
	var fso = new ActiveXObject('Scripting.FileSystemObject')

	// access to filesystem

	var fileWrite
	var fileReadLines

	(function () {

		function correctPath(fileName) {
			return fileName.replace(/\/+/g, "\\")
		}

		fileWrite = function (fileName, data, append) {
			var path = correctPath(fileName)
			var mode = (append ? 8 : 2)
			var fh = fso.OpenTextFile(path, mode, true, 0)
			fh.Write(data)
			fh.Close()
		}

		fileReadLines = function (fileName, callback) {
			var path = correctPath(fileName)
			var fh = fso.OpenTextFile(path, 1, true, 0)
			var res = []
			if (callback) {
				var lineNo = 0
				while (!fh.AtEndOfStream) {
					callback(fh.ReadLine() + "\n", ++ lineNo, fileName)
				}
				res = true
			} else {
				while (!fh.AtEndOfStream) {
					res.push(fh.ReadLine() + "\n")
				}
			}
			fh.Close()
			return res
		}

	})();

	// date formatting function

	function date(fmt, date) {
		var dateformat = {
			'd': function (d) { return d.getDate() },
			'dd': function (d) { var r = d.getDate(); return (r < 10 ? '0' + r : r) },
			'm': function (d) { return d.getMonth() + 1 },
			'mm': function (d) { var r = d.getMonth() + 1; return (r < 10 ? '0' + r : r) },
			'mmm': function (d) { return opts.monthsShort[d.getMonth()] },
			'mmmm': function (d) { return opts.monthsFmt[d.getMonth()] },
			'yy': function (d) { var r = d.getYear() - /*($.browser.msie && $.browser.version <= 8 ? 2000 : 100)*/ 2000; return (r < 10 ? '0' + r : r) },
			'yyyy': function (d) { return d.getYear() + /*($.browser.msie && $.browser.version <= 8 ? 0 : 1900)*/ 0 },
			'h': function (d) { return d.getHours() },
			'hh': function (d) { var r = d.getHours(); return (r < 10 ? '0' + r : r) },
			'i': function (d) { return d.getMinutes() },
			'ii': function (d) { var r = d.getMinutes(); return (r < 10 ? '0' + r : r) },
			's': function (d) { return d.getSeconds() },
			'ss': function (d) { var r = d.getSeconds(); return (r < 10 ? '0' + r : r) }
		}
		date = date || new Date()
		var res = ''
		while (fmt.length > 0) {
			var m = fmt.match(/^(yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ii|i|ss|s)(.*)$/, fmt)
			if (m) {
				res += dateformat[m[1]](date)
				fmt = m[2]
			} else {
				res += fmt.charAt(0)
				fmt = fmt.substr(1)
			}
		}
		return res
	}

	// appending log file

	function logAppend(level, msg) {
		var levelName = [ '', 'ERROR', 'WARNING', 'INFO', 'DEBUG' ]
		if (LOG_FILE && level > 0 && level < levelName.length && level <= LOG_LEVEL) {
			fileWrite(
				LOG_FILE,
				date('yyyy-mm-dd hh:ii:ss')
					+ ' ' + levelName[level] + ' '
					+ msg + "\n",
				true
			)
		}
	}

	// process command line arguments

	for (var i = 0, l = WScript.Arguments.length; i < l; i ++) {
		var arg = "" + WScript.Arguments.item(i)
		if (arg == "--no-reboot") {
			reboot = false
		}
	}
	if (reboot) {
		logAppend(3, "rebooting")
	}

	// crontab parser

	function parseParam(p) {
		var m
		if (p.match(/^\*$/)) {
			return function () {
				return true
			}
		} else if (m = p.match(/^(\d+)$/)) {
			var t = parseInt(m[1])
			return function (val) {
				return val == t
			}
		} else if (m = p.match(/^(\d+)\-(\d+)$/)) {
			var b = parseInt(m[1]), e = parseInt(m[2])
			return function (val) {
				return b <= val && val <= e
			}
		} else if (m = p.match(/^\*\/(\d+)$/)) {
			var d = parseInt(m[1])
			return function (val) {
				return val % d == 0
			}
		}
		return function () {
			return false
		}
	}

	function parseToken(t) {
		var funcs = []
		var p = t.split(/\,+/)
		for (var i = 0; i < p.length; i ++)
			funcs.push(parseParam(p[i]))
		return function (val) {
			for (var i = 0; i < funcs.length; i ++) {
				if (funcs[i](val))
					return true
			}
			return false
		}
	}

	function parseLine(ln, lineNo, fileName) {
		var m
		line = ln.replace(/(^[\r\n]+)|([\r\n]+$)/g, '').replace(/\#.*$/, '')
		if (m = line.match(/^[\s\t]*([0-9\,\/\-\*]+)[\s\t]+([0-9\,\/\-\*]+)[\s\t]+([0-9\,\/\-\*]+)[\s\t]+([0-9\,\/\-\*]+)[\s\t]+([0-9\,\/\-\*]+)[\s\t]+(.+?)$/)) {
			return {
				minute: parseToken(m[1]),
				hour: parseToken(m[2]),
				dayOfMonth: parseToken(m[3]),
				month: parseToken(m[4]),
				dayOfWeek: parseToken(m[5]),
				command: m[6]
			}
		} else if (m = line.match(/^\s*\@(yearly|annually)\s+(.+)$/)) {
			return {
				minute: parseToken('0'),
				hour: parseToken('0'),
				dayOfMonth: parseToken('1'),
				month: parseToken('1'),
				dayOfWeek: parseToken('*'),
				command: m[2]
			}
		} else if (m = line.match(/^\s*\@(monthly)\s+(.+)$/)) {
			return {
				minute: parseToken('0'),
				hour: parseToken('0'),
				dayOfMonth: parseToken('1'),
				month: parseToken('*'),
				dayOfWeek: parseToken('*'),
				command: m[2]
			}
		} else if (m = line.match(/^\s*\@(weekly)\s+(.+)$/)) {
			return {
				minute: parseToken('0'),
				hour: parseToken('0'),
				dayOfMonth: parseToken('*'),
				month: parseToken('*'),
				dayOfWeek: parseToken('0'),
				command: m[2]
			}
		} else if (m = line.match(/^\s*\@(daily|midnight)\s+(.+)$/)) {
			return {
				minute: parseToken('0'),
				hour: parseToken('0'),
				dayOfMonth: parseToken('*'),
				month: parseToken('*'),
				dayOfWeek: parseToken('*'),
				command: m[2]
			}
		} else if (m = line.match(/^\s*\@(hourly)\s+(.+)$/)) {
			return {
				minute: parseToken('0'),
				hour: parseToken('*'),
				dayOfMonth: parseToken('*'),
				month: parseToken('*'),
				dayOfWeek: parseToken('*'),
				command: m[2]
			}
		} else if (m = line.match(/^\s*\@(minutely)\s+(.+)$/)) {
			return {
				minute: parseToken('*'),
				hour: parseToken('*'),
				dayOfMonth: parseToken('*'),
				month: parseToken('*'),
				dayOfWeek: parseToken('*'),
				command: m[2]
			}
		} else if (m = line.match(/^\s*\@(reboot)\s+(.+)$/)) {
			return (reboot ? {
				reboot: parseToken('*'),
				hour: parseToken('*'),
				dayOfMonth: parseToken('*'),
				month: parseToken('*'),
				dayOfWeek: parseToken('*'),
				command: m[2]
			} : null)
		} else if (line.match(/^\s*$/)) {
			// empty line
		} else {
			logAppend(1, fileName + "(" + lineNo + "): invalid format")
		}
		return null
	}

	function parseFile(fileName) {
		try {
			var newTasks = []
			fileReadLines(fileName, function (ln, lnNo, fn) {
				var task = parseLine(ln, lnNo, fn)
				if (task) {
					newTasks.push(task)
				}
			})
			tasks = newTasks
			logAppend(4, "number of tasks: " + tasks.length)
		} catch(ex) {
			logAppend(1, fileName + ": permissions denied")
		}
	}

	// system functions

	function sleep() {
		WScript.Sleep(60000)
	}

	function execCommand(command) {
		logAppend(3, "running command: " + command)
		var rc = sh.Run('%comspec% /c ' + command, 0, false)
		logAppend(4, "command: " + command + " execution RC = " + rc)
	}

	function forEachProcess(callback) {
		var e = new Enumerator(GetObject("winmgmts:").InstancesOf("Win32_process"))
		for (; !e.atEnd(); e.moveNext()) {
			var p = e.item()
			if (callback({
				name: p.Name,
				command: p.CommandLine
			}) === false) {
				break;
			}
		}
	}

	function selfCommandLine() {
		var myNameIs = "" + WScript.ScriptFullName
		var myCommand = ""
		forEachProcess(function (p) {
			var cmd = "" + p.command
			if (/wscript\.exe/i.test(cmd) && cmd.indexOf(myNameIs) >= 0) {
				myCommand = cmd
				return false
			}
		})
		return myCommand
	}

	function cronRepeatCommand() {
		var cmd = selfCommandLine()
		if (cmd.indexOf("--no-reboot") < 0) {
			cmd = cmd.replace(/[\s\r\n\t]+$/, '') + " --no-reboot"
		}
		cmd = cmd.replace(/[\s\r\n\t]+/g, ' ')
		logAppend(4, "cron repeat command: " + cmd)
		return cmd
	}

	// entry point

	function main() {
		var now = new Date()
		parseFile(CRONTAB_FILE)
		for (var i = 0; i < tasks.length; i ++) {
			var task = tasks[i]
			if (
				task.reboot || (
					task.minute(parseInt(now.getMinutes()))
					&& task.hour(parseInt(now.getHours()))
					&& task.dayOfMonth(parseInt(now.getDate()))
					&& task.month(parseInt(now.getMonth()) + 1)
					&& task.dayOfWeek(parseInt(now.getDay()))
				)
			)
				execCommand(task.command)
		}
		sleep()
		sh.Run(cronRepeatCommand(), 0, false)
	}

	main()
})();
